Анализ поведения пользователей мобильного приложения¶

Цель:
На базе информации о действиях, совершенными пользователями в мобильном приложении "Ненужные вещи", провести исследование и выяснить, как можно было бы улучшить приложение с точки зрения пользовательского опыта.

План работы:

  1. Загрузка и предобработка данных
    • импорт библиотек;
    • загрузка датасетов;
    • приведение названий полей к "змеиному стилю";
    • приведение данных в ячейках к оптимальному типу;
    • обработка пропусков;
    • обработка дубликатов;
    • преобразование "show_contacts" в "contacts_show";
    • выводы и наблюдения.
  2. Исследовательский анализ данных
    • оценить количество событий: показать количество событий по типу, отсортированных по убыванию, визуализировать, сделать выводы;
    • оценить количество пользователей, визуализировать по типу источника, сделать выводы;
    • определить период сбора данных, добавить столбец date, визуализировать распределение активности пользователей по дням, сделать выводы;
    • выделить сессии пользователей через тайм-аут сессии;
    • выводы и наблюдения.
  3. Основные вопросы исследования
    • на базе сессий выделить сценарии действия пользователя в приложении;
    • выделить сценарии, приводящие к действию contacts_show ("просмотр контактов");
    • построить воронки по этим сценариям, сделать выводы;
    • рассчитать относительную частоту событий в разрезе двух групп пользователей: те, кто совершал contacts_show, и те, кто не совершал;
    • сравнить, сделать выводы.
  4. Проверка гипотез
    • различие конверсии в просмотры контактов у двух групп пользователей: те, что делают tips_show и tips_click, и те, что делают только tips_show, сделать выводы;
    • различие конверсии в просмотры контактов у двух групп пользователей: те, что просматривают фото (photos_show), и те, что не просматривают; сделать выводы.
  5. Общие выводы

Загрузка и предобработка данных¶

к заголовку

Импорт библиотек¶

In [11]:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import datetime as dt
from scipy import stats as st
import plotly.express as px
import math as mth

Загрузка датасетов¶

Датасет mobile_dataset.csv¶

In [12]:
data = pd.read_csv('https://code.s3.yandex.net/datasets/mobile_dataset.csv')
data.info()
display(data.head(10))
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 74197 entries, 0 to 74196
Data columns (total 3 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   event.time  74197 non-null  object
 1   event.name  74197 non-null  object
 2   user.id     74197 non-null  object
dtypes: object(3)
memory usage: 1.7+ MB
event.time event.name user.id
0 2019-10-07 00:00:00.431357 advert_open 020292ab-89bc-4156-9acf-68bc2783f894
1 2019-10-07 00:00:01.236320 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
2 2019-10-07 00:00:02.245341 tips_show cf7eda61-9349-469f-ac27-e5b6f5ec475c
3 2019-10-07 00:00:07.039334 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
4 2019-10-07 00:00:56.319813 advert_open cf7eda61-9349-469f-ac27-e5b6f5ec475c
5 2019-10-07 00:01:19.993624 tips_show cf7eda61-9349-469f-ac27-e5b6f5ec475c
6 2019-10-07 00:01:27.770232 advert_open 020292ab-89bc-4156-9acf-68bc2783f894
7 2019-10-07 00:01:34.804591 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
8 2019-10-07 00:01:49.732803 advert_open cf7eda61-9349-469f-ac27-e5b6f5ec475c
9 2019-10-07 00:01:54.958298 advert_open 020292ab-89bc-4156-9acf-68bc2783f894

Описание столбцов:
event.time - время события;
event.name - вид события;
user.id - уникальный идентификатор пользоователя.

Описание событий, столбец event.name:
advert_open - пользователь открыл карточку объявления после поиска (в приложении или из стороннего ресурса);
photos_show - пользователь посмотрел фотографию;
tips_show - увидел рекомендованные объявления; tips_click - кликнул по рекомендованному объявлению;
contacts_show и show_contacts - посмотрел контакты продавца;
contacts_call - позвонил продавцу;
map - открыл карту объявлений;
search1 ... search7 - поиск по сайту;
favorites_add - добавил объявление в "Избранные".

Датасет mobile_sources.csv¶

In [13]:
source = pd.read_csv('https://code.s3.yandex.net/datasets/mobile_sources.csv')
source.info()
display(source.head(10))
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4293 entries, 0 to 4292
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   userId  4293 non-null   object
 1   source  4293 non-null   object
dtypes: object(2)
memory usage: 67.2+ KB
userId source
0 020292ab-89bc-4156-9acf-68bc2783f894 other
1 cf7eda61-9349-469f-ac27-e5b6f5ec475c yandex
2 8c356c42-3ba9-4cb6-80b8-3f868d0192c3 yandex
3 d9b06b47-0f36-419b-bbb0-3533e582a6cb other
4 f32e1e2a-3027-4693-b793-b7b3ff274439 google
5 17f6b2db-2964-4d11-89d8-7e38d2cb4750 yandex
6 62aa104f-592d-4ccb-8226-2ba0e719ded5 yandex
7 57321726-5d66-4d51-84f4-c797c35dcf2b google
8 c2cf55c0-95f7-4269-896c-931d14deaab5 google
9 48e614d6-fe03-40f7-bf9e-4c4f61c19f64 yandex

Описание столбцов:
userId - уникальный идентификатор пользователя;
source - источник, с которого пользователь установил приложение.

Выводы и наблюдения:
Данные не содержат пропусков. Названия некоторых столбцов нужно привести к "змеиному" стилю. Данные в столбце event.time привести к типу datetime.

Переименование столбцов¶

In [14]:
data.columns = ['event_time','event_name','user_id']
source.columns = ['user_id','source']

Приведение значений в столбце event_time к типу datetime¶

In [15]:
data['event_time'] = pd.to_datetime(data['event_time'])
data.info()
display(data.head(10))
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 74197 entries, 0 to 74196
Data columns (total 3 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   event_time  74197 non-null  datetime64[ns]
 1   event_name  74197 non-null  object        
 2   user_id     74197 non-null  object        
dtypes: datetime64[ns](1), object(2)
memory usage: 1.7+ MB
event_time event_name user_id
0 2019-10-07 00:00:00.431357 advert_open 020292ab-89bc-4156-9acf-68bc2783f894
1 2019-10-07 00:00:01.236320 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
2 2019-10-07 00:00:02.245341 tips_show cf7eda61-9349-469f-ac27-e5b6f5ec475c
3 2019-10-07 00:00:07.039334 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
4 2019-10-07 00:00:56.319813 advert_open cf7eda61-9349-469f-ac27-e5b6f5ec475c
5 2019-10-07 00:01:19.993624 tips_show cf7eda61-9349-469f-ac27-e5b6f5ec475c
6 2019-10-07 00:01:27.770232 advert_open 020292ab-89bc-4156-9acf-68bc2783f894
7 2019-10-07 00:01:34.804591 tips_show 020292ab-89bc-4156-9acf-68bc2783f894
8 2019-10-07 00:01:49.732803 advert_open cf7eda61-9349-469f-ac27-e5b6f5ec475c
9 2019-10-07 00:01:54.958298 advert_open 020292ab-89bc-4156-9acf-68bc2783f894

Обработка дубликатов¶

Явные дубликаты¶

In [16]:
print('Количество дубликатов в датасете data: ',data.duplicated().sum())
print('Количество дубликатов в датасете source: ',source.duplicated().sum())
Количество дубликатов в датасете data:  0
Количество дубликатов в датасете source:  0

Неявные дубликаты¶

In [17]:
print(source['source'].unique())
['other' 'yandex' 'google']

В датасете source неявных дубликатов нет.

In [18]:
print(data['event_name'].unique())
['advert_open' 'tips_show' 'map' 'contacts_show' 'search_4' 'search_5'
 'tips_click' 'photos_show' 'search_1' 'search_2' 'search_3'
 'favorites_add' 'contacts_call' 'search_6' 'search_7' 'show_contacts']

Два значения, contacts_show и show_contacts, обозначающие одно и тоже событие, возникли, вероятно, при сборе информации. Поставим везде contacts_show и снова проверим на явные дубликаты.

In [19]:
data['event_name'] = data['event_name'].str.replace('show_contacts','contacts_show')
# проверим результат
print(data['event_name'].unique())
['advert_open' 'tips_show' 'map' 'contacts_show' 'search_4' 'search_5'
 'tips_click' 'photos_show' 'search_1' 'search_2' 'search_3'
 'favorites_add' 'contacts_call' 'search_6' 'search_7']
In [20]:
# проверим на явные дубликаты
print('Количество дубликатов в датасете data: ',data.duplicated().sum())
Количество дубликатов в датасете data:  0

Итоги раздела¶

Мы загрузили и изучили два датасета с информацией о действиях, совершенных пользователями в приложении.
Проверили на наличие пропусков и дубликатов. Таковых не оказалось.
Привели названия столбцов в датасетах к "змеиному" стилю.
Оптимизировали тип данных в столбце event_time датасета data.


Исследовательский анализ данных¶

к заголовку

Оценка количества событий, визуализация количества событий по типу¶

In [21]:
event_name_n = data.groupby('event_name')['user_id'].count().sort_values(ascending=False)
display(event_name_n)
event_name
tips_show        40055
photos_show      10012
advert_open       6164
contacts_show     4529
map               3881
search_1          3506
favorites_add     1417
search_5          1049
tips_click         814
search_4           701
contacts_call      541
search_3           522
search_6           460
search_2           324
search_7           222
Name: user_id, dtype: int64
In [22]:
plt.figure(figsize=(14,8), dpi=100)
sns.barplot(x=event_name_n, y=event_name_n.index, orient='h', color = '#5566ca')
plt.title('Количество событий по типу', fontsize=14)
plt.xlabel('Количество событий')
plt.ylabel('Событие')
plt.grid();
No description has been provided for this image
In [23]:
print(data.query('event_name == "tips_show"')['user_id'].nunique())
2801

Оценка количества пользователей, визуализация по типу источника¶

In [24]:
print('Количество уникальных пользователей: ', data['user_id'].nunique())
Количество уникальных пользователей:  4293

Количество уникальных пользователей в двух датасетах совпадает - 4293.
Посмотрим, каким источником они пользовались для установки приложения.

In [25]:
source_type_n = source.groupby('source').count().sort_values(by='user_id',ascending=False)
display(source_type_n)
user_id
source
yandex 1934
other 1230
google 1129
In [26]:
plt.figure(figsize=(6,5), dpi=100)
sns.barplot(y=source_type_n['user_id'], x=source_type_n.index)
plt.title('Количество источников по типу', fontsize=12)
plt.xlabel('Источник')
plt.ylabel('Количество источников');
No description has been provided for this image

У Яндекса минимум в полтора раза больше скачиваний приложения "Ненужные вещи", чем у других источников.

Определение периода сбора данных, добавление столбца date, визуализация активности пользователей по дням¶

In [27]:
print('Начало наблюдений: ', data['event_time'].min())
print('Завершение наблюдений: ', data['event_time'].max())
Начало наблюдений:  2019-10-07 00:00:00.431357
Завершение наблюдений:  2019-11-03 23:58:12.532487

Имеем данные за 28 полных дней, ровно четыре недели, с понедельника по воскресенье.

Добавим в датасет столбец с датой:

In [28]:
data['date'] = data['event_time'].dt.date
display(data.head())
data.info()
event_time event_name user_id date
0 2019-10-07 00:00:00.431357 advert_open 020292ab-89bc-4156-9acf-68bc2783f894 2019-10-07
1 2019-10-07 00:00:01.236320 tips_show 020292ab-89bc-4156-9acf-68bc2783f894 2019-10-07
2 2019-10-07 00:00:02.245341 tips_show cf7eda61-9349-469f-ac27-e5b6f5ec475c 2019-10-07
3 2019-10-07 00:00:07.039334 tips_show 020292ab-89bc-4156-9acf-68bc2783f894 2019-10-07
4 2019-10-07 00:00:56.319813 advert_open cf7eda61-9349-469f-ac27-e5b6f5ec475c 2019-10-07
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 74197 entries, 0 to 74196
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   event_time  74197 non-null  datetime64[ns]
 1   event_name  74197 non-null  object        
 2   user_id     74197 non-null  object        
 3   date        74197 non-null  object        
dtypes: datetime64[ns](1), object(3)
memory usage: 2.3+ MB

Проверим полноту данных, визуализируем количество событий по дням, а заодно количество целевых событий contacts_show по дням.

In [29]:
data_date_n = data.groupby(by=['date']).agg({'event_name':'count'}).reset_index()
contacts_show_n = (data
                   .query('event_name == "contacts_show"')
                   .groupby(by=['date']).agg({'event_name':'count'})
                   .reset_index()
                  )
plt.figure(figsize=(15,8), dpi=100)
sns.barplot(x='date', y='event_name', data=data_date_n, color='#5566ca',label='все события')
sns.barplot(x='date', y='event_name', data=contacts_show_n, color='lightgreen',label='contacts_show')
plt.title('Количество событий по дням', fontsize=14)
plt.xlabel('Дата')
plt.ylabel('Количество событий')
plt.xticks(rotation=30)
plt.axhline(data_date_n['event_name'].mean(), color='black', linestyle='--',label='среднее количество событий')
plt.axhline(contacts_show_n['event_name'].mean(), color='darkgreen', linestyle='--',label='среднее количество событий contacts_show')
plt.legend()
plt.show();
No description has been provided for this image

Количество событий колеблется около 2650. Тренд за месяц определить нельзя, но, кажется, в первую неделю событий было меньше.
Целевое действие contacts_show совершается также с равной, в среднем, интенсивностью, не считая первой недели, где интенсивность явно меньше.

Выделение сессий¶

Для того, чтобы в дальнейшем найти все пользовательские сценарии действий, сначала разобьем имеющиеся события на последовательности - сессии.
Перед поиском сессий отсортируем датасет:

In [30]:
data_sorted = data.sort_values(by=['user_id', 'event_time'])
display(data_sorted.head())
event_time event_name user_id date
805 2019-10-07 13:39:45.989359 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07
806 2019-10-07 13:40:31.052909 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07
809 2019-10-07 13:41:05.722489 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07
820 2019-10-07 13:43:20.735461 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07
830 2019-10-07 13:45:30.917502 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07

Добавим столбец с разностью времени в секундах между настоящим и предыдущим событиями:

In [31]:
data_sorted['time_diff'] = data_sorted['event_time'].diff().dt.total_seconds()
display(data_sorted.head(60))
data_sorted.info()
event_time event_name user_id date time_diff
805 2019-10-07 13:39:45.989359 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 NaN
806 2019-10-07 13:40:31.052909 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 4.506355e+01
809 2019-10-07 13:41:05.722489 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 3.466958e+01
820 2019-10-07 13:43:20.735461 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 1.350130e+02
830 2019-10-07 13:45:30.917502 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 1.301820e+02
831 2019-10-07 13:45:43.212340 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 1.229484e+01
832 2019-10-07 13:46:31.033718 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 4.782138e+01
836 2019-10-07 13:47:32.860234 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 6.182652e+01
839 2019-10-07 13:49:41.716617 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 1.288564e+02
6541 2019-10-09 18:33:55.577963 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 1.898539e+05
6546 2019-10-09 18:35:28.260975 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 9.268301e+01
6565 2019-10-09 18:40:28.738785 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 3.004778e+02
6566 2019-10-09 18:42:22.963948 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 1.142252e+02
36412 2019-10-21 19:52:30.778932 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.041008e+06
36416 2019-10-21 19:53:17.165009 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 4.638608e+01
36419 2019-10-21 19:53:38.767230 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 2.160222e+01
36421 2019-10-21 19:54:45.009859 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 6.624263e+01
36423 2019-10-21 19:54:56.854811 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.184495e+01
36430 2019-10-21 19:56:49.417415 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.125626e+02
36435 2019-10-21 19:57:21.124551 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 3.170714e+01
36437 2019-10-21 19:57:49.029206 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 2.790466e+01
36447 2019-10-21 20:00:00.438922 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.314097e+02
36454 2019-10-21 20:01:16.839387 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 7.640047e+01
36459 2019-10-21 20:01:52.099777 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 3.526039e+01
36473 2019-10-21 20:05:04.173348 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.920736e+02
36481 2019-10-21 20:06:47.035115 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.028618e+02
36486 2019-10-21 20:07:30.051028 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 4.301591e+01
37556 2019-10-22 11:18:14.635436 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 5.464458e+04
37559 2019-10-22 11:19:10.529462 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 5.589403e+01
37566 2019-10-22 11:20:12.571696 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 6.204223e+01
37571 2019-10-22 11:21:30.964099 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 7.839240e+01
37581 2019-10-22 11:25:33.508919 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 2.425448e+02
37591 2019-10-22 11:28:05.165918 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 1.516570e+02
37601 2019-10-22 11:30:05.522265 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 1.203563e+02
37607 2019-10-22 11:30:52.807203 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 4.728494e+01
31632 2019-10-19 21:34:33.849769 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 -2.229790e+05
31636 2019-10-19 21:35:19.296599 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 4.544683e+01
31640 2019-10-19 21:36:44.344691 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 8.504809e+01
31655 2019-10-19 21:40:38.990477 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 2.346458e+02
31659 2019-10-19 21:42:13.837523 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 9.484705e+01
31670 2019-10-19 21:44:55.589731 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 1.617522e+02
31685 2019-10-19 21:46:52.541309 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 1.169516e+02
31730 2019-10-19 21:58:00.109019 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 6.675677e+02
31737 2019-10-19 21:59:54.637098 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 1.145281e+02
33482 2019-10-20 18:49:24.115634 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 7.496948e+04
33498 2019-10-20 18:59:22.541082 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 5.984254e+02
33510 2019-10-20 19:03:02.030004 favorites_add 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 2.194889e+02
33514 2019-10-20 19:04:16.149734 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 7.411973e+01
33523 2019-10-20 19:09:56.162564 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 3.400128e+02
33528 2019-10-20 19:11:47.344296 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 1.111817e+02
33533 2019-10-20 19:17:18.659799 contacts_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 3.313155e+02
33534 2019-10-20 19:17:24.887762 contacts_call 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 6.227963e+00
33537 2019-10-20 19:18:54.738758 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 8.985100e+01
33540 2019-10-20 19:20:41.699609 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 1.069609e+02
33544 2019-10-20 19:23:11.839947 contacts_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 1.501403e+02
33545 2019-10-20 19:23:14.236973 contacts_call 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 2.397026e+00
33565 2019-10-20 19:30:31.912891 contacts_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 4.376759e+02
33566 2019-10-20 19:30:36.096917 contacts_call 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 4.184026e+00
33601 2019-10-20 19:57:15.652784 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 1.599556e+03
33615 2019-10-20 20:04:16.954396 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 4.213016e+02
<class 'pandas.core.frame.DataFrame'>
Int64Index: 74197 entries, 805 to 72689
Data columns (total 5 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   event_time  74197 non-null  datetime64[ns]
 1   event_name  74197 non-null  object        
 2   user_id     74197 non-null  object        
 3   date        74197 non-null  object        
 4   time_diff   74196 non-null  float64       
dtypes: datetime64[ns](1), float64(1), object(3)
memory usage: 3.4+ MB

Построим распределение разницы времени между соседними событиями в сессии.
Ограничим значения нулем слева и одним часом, т.е. 3600 секундами, справа:

In [32]:
plt.figure(figsize=(8,5), dpi=100)
plt.hist(data_sorted.query('time_diff > 0 and time_diff < 3600')['time_diff'], bins=100)
plt.title('Распределение разницы времени между соседними событиями в сессии', fontsize=10)
plt.xlabel('Разница времени')
plt.ylabel('Количество наблюдений');
No description has been provided for this image

Для более точного определения тайм-аута сессии найдем 95-й перцентиль среди значений столбца time_diff и будем считать его порогом, выше которого будут находиться нехарактерные для внутрисессионных интервалов значения разницы времени:

In [33]:
session_timeout = np.percentile(data_sorted.query('time_diff > 0 and time_diff < 3600')['time_diff'],95)
print('Тайм-аут сессии = ', session_timeout.round(1), 'секунды')
Тайм-аут сессии =  579.6 секунды

Т.е. чуть больше девяти с половиной минут.

Добавим в датасет столбец с номером сессии, к которой относится каждое событие:

In [34]:
g = (data_sorted.groupby('user_id')['event_time'].diff().dt.total_seconds() > session_timeout).cumsum()
data_sorted['session_id'] = data_sorted.groupby(['user_id', g], sort=False).ngroup() + 1
display(data_sorted.head(60),data_sorted.tail(5))
event_time event_name user_id date time_diff session_id
805 2019-10-07 13:39:45.989359 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 NaN 1
806 2019-10-07 13:40:31.052909 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 4.506355e+01 1
809 2019-10-07 13:41:05.722489 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 3.466958e+01 1
820 2019-10-07 13:43:20.735461 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 1.350130e+02 1
830 2019-10-07 13:45:30.917502 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 1.301820e+02 1
831 2019-10-07 13:45:43.212340 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 1.229484e+01 1
832 2019-10-07 13:46:31.033718 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 4.782138e+01 1
836 2019-10-07 13:47:32.860234 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 6.182652e+01 1
839 2019-10-07 13:49:41.716617 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 1.288564e+02 1
6541 2019-10-09 18:33:55.577963 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 1.898539e+05 2
6546 2019-10-09 18:35:28.260975 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 9.268301e+01 2
6565 2019-10-09 18:40:28.738785 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 3.004778e+02 2
6566 2019-10-09 18:42:22.963948 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 1.142252e+02 2
36412 2019-10-21 19:52:30.778932 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.041008e+06 3
36416 2019-10-21 19:53:17.165009 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 4.638608e+01 3
36419 2019-10-21 19:53:38.767230 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 2.160222e+01 3
36421 2019-10-21 19:54:45.009859 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 6.624263e+01 3
36423 2019-10-21 19:54:56.854811 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.184495e+01 3
36430 2019-10-21 19:56:49.417415 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.125626e+02 3
36435 2019-10-21 19:57:21.124551 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 3.170714e+01 3
36437 2019-10-21 19:57:49.029206 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 2.790466e+01 3
36447 2019-10-21 20:00:00.438922 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.314097e+02 3
36454 2019-10-21 20:01:16.839387 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 7.640047e+01 3
36459 2019-10-21 20:01:52.099777 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 3.526039e+01 3
36473 2019-10-21 20:05:04.173348 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.920736e+02 3
36481 2019-10-21 20:06:47.035115 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 1.028618e+02 3
36486 2019-10-21 20:07:30.051028 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 4.301591e+01 3
37556 2019-10-22 11:18:14.635436 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 5.464458e+04 4
37559 2019-10-22 11:19:10.529462 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 5.589403e+01 4
37566 2019-10-22 11:20:12.571696 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 6.204223e+01 4
37571 2019-10-22 11:21:30.964099 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 7.839240e+01 4
37581 2019-10-22 11:25:33.508919 map 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 2.425448e+02 4
37591 2019-10-22 11:28:05.165918 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 1.516570e+02 4
37601 2019-10-22 11:30:05.522265 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 1.203563e+02 4
37607 2019-10-22 11:30:52.807203 tips_show 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 4.728494e+01 4
31632 2019-10-19 21:34:33.849769 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 -2.229790e+05 5
31636 2019-10-19 21:35:19.296599 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 4.544683e+01 5
31640 2019-10-19 21:36:44.344691 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 8.504809e+01 5
31655 2019-10-19 21:40:38.990477 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 2.346458e+02 5
31659 2019-10-19 21:42:13.837523 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 9.484705e+01 5
31670 2019-10-19 21:44:55.589731 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 1.617522e+02 5
31685 2019-10-19 21:46:52.541309 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 1.169516e+02 5
31730 2019-10-19 21:58:00.109019 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 6.675677e+02 6
31737 2019-10-19 21:59:54.637098 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 1.145281e+02 6
33482 2019-10-20 18:49:24.115634 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 7.496948e+04 7
33498 2019-10-20 18:59:22.541082 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 5.984254e+02 8
33510 2019-10-20 19:03:02.030004 favorites_add 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 2.194889e+02 8
33514 2019-10-20 19:04:16.149734 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 7.411973e+01 8
33523 2019-10-20 19:09:56.162564 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 3.400128e+02 8
33528 2019-10-20 19:11:47.344296 search_1 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 1.111817e+02 8
33533 2019-10-20 19:17:18.659799 contacts_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 3.313155e+02 8
33534 2019-10-20 19:17:24.887762 contacts_call 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 6.227963e+00 8
33537 2019-10-20 19:18:54.738758 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 8.985100e+01 8
33540 2019-10-20 19:20:41.699609 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 1.069609e+02 8
33544 2019-10-20 19:23:11.839947 contacts_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 1.501403e+02 8
33545 2019-10-20 19:23:14.236973 contacts_call 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 2.397026e+00 8
33565 2019-10-20 19:30:31.912891 contacts_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 4.376759e+02 8
33566 2019-10-20 19:30:36.096917 contacts_call 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 4.184026e+00 8
33601 2019-10-20 19:57:15.652784 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 1.599556e+03 9
33615 2019-10-20 20:04:16.954396 photos_show 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 4.213016e+02 9
event_time event_name user_id date time_diff session_id
72584 2019-11-03 15:51:23.959572 tips_show fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 27.886483 12801
72589 2019-11-03 15:51:57.899997 contacts_show fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 33.940425 12801
72684 2019-11-03 16:07:40.932077 tips_show fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 943.032080 12802
72688 2019-11-03 16:08:18.202734 tips_show fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 37.270657 12802
72689 2019-11-03 16:08:25.388712 tips_show fffb9e79-b927-4dbb-9b48-7fd09b23a62b 2019-11-03 7.185978 12802

Всего 12802 сессии.

Итоги раздела:¶

В этом разделе проекта мы проводили исследовательский анализ входных данных.

  1. Оценили общее количество событий по их видам:
  • первое место, 40000 из 74000 - главная страница, показ рекомендованных объявлений, - с учетом количества пользователей в среднем по 10 событий на пользователя;
  • просмотр фотографий - второе место - 10000 событий;
  • целевое событие, contacts_show, на четвертом месте, 4500 событий, это чуть больше, чем по одному такому событию на пользователя в среднем.
  1. Нашли, как пользователи распределены по источникам установки приложения:
    Яндекс с полуторакратным отрывом от примерно равных результатов у Гугл и других источников.
  2. Определили период исследования, визуализировали активность пользователей по дням:
  • период исследования с 07.10.2019 по 03.11.2019 - ровно четыре недели с понедельника по воскресенье;
  • ежедневное количество событий за этот период колеблется от 1800 до 3300, среднее значение - 2650;
  • целевое событие contacts_show ежедневно случается, в среднем 170 раз;
  • на визуализации заметно, что в первую неделю наблюдений активность пользователей была чуть меньше.
  1. Выделили сессии из общего потока событий:
    всего выделено 12802 сессии, т.е. в среднем чуть больше, чем по три сессии на пользователя.

Основные вопросы исследования¶

к заголовку

Выделение пользовательских сценариев¶

Определим какие последовательности событий присутствуют в сессиях:

In [35]:
# объединим все действия поиска в один 'search'
# функция для замены значения `search..`
def change_search(string):
    if 'search' in string:
        return 'search'
    else: return string
# применим ф-ию для столбца `event_name`        
data_sorted['event_name'] = data_sorted['event_name'].apply(change_search)
# проверим результат
print(data_sorted['event_name'].unique())

# сгруппируем по номеру сессии
sessions = data_sorted.groupby('session_id',as_index=False).agg({'event_name':'unique','user_id':'first','event_time':'min'})
display(sessions.head(10))
['tips_show' 'map' 'search' 'photos_show' 'favorites_add' 'contacts_show'
 'contacts_call' 'advert_open' 'tips_click']
session_id event_name user_id event_time
0 1 [tips_show] 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-07 13:39:45.989359
1 2 [map, tips_show] 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-09 18:33:55.577963
2 3 [tips_show, map] 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-21 19:52:30.778932
3 4 [map, tips_show] 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 2019-10-22 11:18:14.635436
4 5 [search, photos_show] 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 21:34:33.849769
5 6 [search, photos_show] 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-19 21:58:00.109019
6 7 [search] 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 18:49:24.115634
7 8 [photos_show, favorites_add, search, contacts_... 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 18:59:22.541082
8 9 [photos_show, contacts_show] 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-20 19:57:15.652784
9 10 [photos_show, advert_open] 00157779-810c-4498-9e05-a1e9e3cedf93 2019-10-24 10:50:40.219833

Получим таблицу со всевозможными сценариями и их количеством, отсортируем по убыванию:

In [36]:
# преобразуем ndarray в tuple, чтобы можно было сгруппировать по сценарию
sessions['event_name'] = sessions['event_name'].apply(tuple)
In [37]:
# сгруппируем по `event_name` и выведем результат
session_scrypts = sessions.groupby('event_name',as_index=False)['session_id'].count()
display(session_scrypts.sort_values('session_id',ascending=False).head(10))
print('Всего сценариев:',len(session_scrypts))
event_name session_id
279 (tips_show,) 3775
166 (photos_show,) 1716
196 (search,) 991
148 (map, tips_show) 590
237 (search, photos_show) 513
289 (tips_show, contacts_show) 451
249 (search, tips_show) 415
44 (contacts_show,) 318
312 (tips_show, map) 231
28 (advert_open, tips_show) 212
Всего сценариев: 354

Выделение целевых сценариев¶

Выделим из общего количества только те сценарии, где есть целевое действие contacts_show, и оно последнее:

In [38]:
# функция для проверки сценария на целевой
def target(list):
    if list[-1] == 'contacts_show':                 # если последний элемент списка равен "contacts_show", возвращаем True, нет - False
        return True
    else: return False

# получаем датафрейм с целевыми сценариями, применяя ф-ию `target`
target_scrypts = session_scrypts[session_scrypts['event_name'].apply(target)]

display(target_scrypts.sort_values('session_id', ascending=False).head(20))
print('Всего сценариев:',len(target_scrypts))
event_name session_id
289 (tips_show, contacts_show) 451
44 (contacts_show,) 318
171 (photos_show, contacts_show) 88
155 (map, tips_show, contacts_show) 87
203 (search, contacts_show) 58
240 (search, photos_show, contacts_show) 36
251 (search, tips_show, contacts_show) 28
320 (tips_show, map, contacts_show) 22
346 (tips_show, tips_click, contacts_show) 20
1 (advert_open, contacts_show) 16
124 (map, advert_open, tips_show, contacts_show) 13
129 (map, contacts_show) 12
29 (advert_open, tips_show, contacts_show) 9
232 (search, map, tips_show, contacts_show) 8
189 (photos_show, search, contacts_show) 7
94 (favorites_add, contacts_show) 6
281 (tips_show, advert_open, contacts_show) 6
161 (map, tips_show, favorites_add, contacts_show) 5
226 (search, map, advert_open, tips_show, contacts... 5
117 (map, advert_open, contacts_show) 4
Всего сценариев: 54

Воронки событий наиболее популярных сценариев¶

Визуализируем воронки событий по следующим выбранным сценариям, где целевым событием является contacts_show:
'tips_show' -> 'contacts_show'
'photos_show' -> 'contacts_show'
'map' -> 'tips_show' -> 'contacts_show'
'search' -> 'contacts_show'

In [39]:
# создадим функцию для получения данных для воронки и ее отрисовки:
def show_funnel(events):      # на входе - список из названий событий - сценарий
    users = []              # список пользователей
    start = True             # признак начала, равен True перед выполнением первого цикла, чтобы не сравнивать список пользователей с пустым списком 
    funnel = pd.DataFrame(columns=['users_amount','total_percent'],index=events)          # датасет с данными для построения воронки
  # для каждого события из входного сценария:
    for e in events:
      # находим список уников для события, текущий список
        users_c = list(data_sorted.query('event_name == @e')['user_id'].unique())
      # проверка начала 
        if not start:                              # для каждого пользователя в текущем списке проверяем наличие этого пользователя 
            temp_list = []                         # в предыдущем списке пользователей и, если обнаружили, добавляем во временный список
            for u in users_c:                      
                if u in users:
                    temp_list.append(u) 
            users_c = temp_list
        start = False                              # устанавливаем тэг начала в значение False
        users = users_c                                    # обновляем список пользователей
        funnel.loc[e]['users_amount'] = len(users_c)       # и заносим количество уников в датасет воронки
        funnel.loc[e]['total_percent'] = round(100*(len(users_c)/len(source)),2)       # добавляем процент от общего количества
        
    funnel['conversion'] = funnel['total_percent'].shift()                          # новый столбец "конверсия" создается сдвигом столбца с процентами на одну строку вниз
    funnel.loc[events[0]]['conversion'] =  funnel.loc[events[0]]['total_percent']     # равняем значения, чтобы потом получить в этой ячейке 100%
    funnel['conversion'] = 100*funnel['total_percent'] / funnel['conversion']            # считаем столбец конверсии
  # вывод таблицы 
  # display(funnel)
  # вывод визуализации
    fig = px.funnel(funnel, y='conversion', x=funnel.index, title = 'Воронка конверсии событий для сценария '+str(events))
    fig.show()

# применяем фунцию draw_funnel
show_funnel(['tips_show', 'contacts_show'])
show_funnel(['photos_show', 'contacts_show'])
show_funnel(['map','tips_show', 'contacts_show'])
show_funnel(['search', 'contacts_show'])

Так или иначе, разными путями, но пользователи пытаются узнать контакты продавцов. По представленным сценариям сложно определить, какие из них точно лучше, чем другие, но бОльшая конверсия в целевое действие - у сценария [photos_show -> contacts_show].
Самая слабая конверсия в последовательностях tips_show -> contacts_show и search -> contacts_show. Возможно, плохо работает поиск, показывая пользователям не совсем тот продукт, который они искали.

Расчет относительной частоты событий¶

Оценим, какие действия чаще совершают те пользователи, которые просматривают контакты.
Рассчитаем относительную частоту событий для двух групп пользователей:

  • те, которые совершали contacts_show;
  • и те, которые не совершали contacts_show.
In [40]:
# список пользователей, совершавших `contacts_show`
target_users = data_sorted.query('event_name == "contacts_show"')['user_id'].unique()
print('Количество пользователей, совершавших `contacts_show`:'
      ,len(target_users), 'из', len(source),', т.е.'
      ,round(100*len(target_users)/len(source),2),'%')
Количество пользователей, совершавших `contacts_show`: 981 из 4293 , т.е. 22.85 %
In [41]:
# относительная частота событий целевых пользователей
target_users_nevents = (data_sorted
                        .query('user_id in @target_users')
                        .groupby('event_name',as_index=False)['user_id'].count()
                       )
# удалим события `contacts_show` и `contacts_call`, т.к. пользователи из другой группы их не совершают
target_users_nevents = target_users_nevents.query('event_name != "contacts_show" and event_name != "contacts_call"')
# посчитаем столбец с долей событий в процентах
target_users_nevents['percent'] = 100*target_users_nevents['user_id']/target_users_nevents['user_id'].sum()
# отсортируем по убыванию величины доли
target_users_nevents = target_users_nevents.sort_values('percent',ascending=0)
display(target_users_nevents)
event_name user_id percent
8 tips_show 12768 57.703258
5 photos_show 3828 17.300131
6 search 2084 9.418358
0 advert_open 1589 7.181272
4 map 1101 4.975821
3 favorites_add 424 1.916211
7 tips_click 333 1.504949
In [42]:
# относительная частота событий нецелевых пользователей
nontarget_users_nevents = (data_sorted
                        .query('user_id not in @target_users')
                        .groupby('event_name',as_index=False)['user_id'].count()
                       )
nontarget_users_nevents['percent'] = 100*nontarget_users_nevents['user_id']/nontarget_users_nevents['user_id'].sum()
nontarget_users_nevents = nontarget_users_nevents.sort_values('percent',ascending=0)
display(nontarget_users_nevents)
event_name user_id percent
6 tips_show 27287 58.057447
3 photos_show 6184 13.157447
4 search 4700 10.000000
0 advert_open 4575 9.734043
2 map 2780 5.914894
1 favorites_add 993 2.112766
5 tips_click 481 1.023404
In [43]:
# объединяем в один датафрейм
target_users_nevents['tag'] = 'пользователи, совершавшие `contacts_show`'
nontarget_users_nevents['tag'] = 'пользователи, не совершавшие `contacts_show`'
users_nevents = pd.concat([target_users_nevents,nontarget_users_nevents])
users_nevents.columns = ['event_name','amount','percent','tag']

display(users_nevents)
event_name amount percent tag
8 tips_show 12768 57.703258 пользователи, совершавшие `contacts_show`
5 photos_show 3828 17.300131 пользователи, совершавшие `contacts_show`
6 search 2084 9.418358 пользователи, совершавшие `contacts_show`
0 advert_open 1589 7.181272 пользователи, совершавшие `contacts_show`
4 map 1101 4.975821 пользователи, совершавшие `contacts_show`
3 favorites_add 424 1.916211 пользователи, совершавшие `contacts_show`
7 tips_click 333 1.504949 пользователи, совершавшие `contacts_show`
6 tips_show 27287 58.057447 пользователи, не совершавшие `contacts_show`
3 photos_show 6184 13.157447 пользователи, не совершавшие `contacts_show`
4 search 4700 10.000000 пользователи, не совершавшие `contacts_show`
0 advert_open 4575 9.734043 пользователи, не совершавшие `contacts_show`
2 map 2780 5.914894 пользователи, не совершавшие `contacts_show`
1 favorites_add 993 2.112766 пользователи, не совершавшие `contacts_show`
5 tips_click 481 1.023404 пользователи, не совершавшие `contacts_show`

Сравнить долю тех или иных событий будет проще на графике:

In [44]:
plt.figure(figsize=(12,6), dpi=100)
sns.barplot(x='event_name', y='percent', data=users_nevents, hue='tag')
plt.title('Относительная частота событий', fontsize=14)
plt.xlabel('События')
plt.ylabel('Процент от общего количества событий в группе')
plt.legend()
plt.show();
No description has been provided for this image

Из графика видно, что доля события photos_show и доля события tips_click у пользователей, совершавших contacts_show, в 1,3 и в 1,5 раза соответственно выше, чем у остальных пользователей. Относительная частота событий advert_open, search и map, наоборот, ниже. Можно сделать вывод, что решение о покупке пользователь делает преимущественно на основе просмотра фотографий.
Также мы выяснили, что действие contacts_call совершают только те, кто совершил contacts_show.

Итоги раздела:¶

В этом разделе мы подробно рассматривали влияние других событий на целевое, contacts_show.

  1. Из ранее полученных сессий мы выделили сценарии - последовательности событий. Всего определили 354 различных сценария.
  2. Из сценариев выделили те, которые приводят в итоге к целевому событию, а также рассчитали их количество. Получилось 54 целевых сценария.
  3. По наиболее частым сценариям построили воронки конверсии, наиболее эффективным из выбранных оказался переход от photos_show к contacts_show.
  4. Определили и визуализировали относительную частоту событий для двух групп пользователей: одна группа - те, кто совершал contacts_show, другая - те, кто не совершал. Выяснилось, что пользователи, совершавшие целевое действие, чаще просматривают фотографии, чем остальные. Что согласуется с предыдущим пунктом. Также они чаще кликают рекомендованные объявления.

Проверка гипотез¶

к заголовку

Гипотеза о различии конверсии в просмотр контактов у двух групп пользователей: те, которые совершают действия tips_show и tips_click, и те, которые совершают только tips_show.¶

  • Нулевой гипотезой, Но, будет гипотеза о равенстве конверсии между "кликерами" (те, кто совершают tips_click) и "некликерами" (те, кто не кликают по рекомендациям) в просмотр контактов (contacts_show).
  • Альтернативной гипотезой, На, будет гипотеза о различии в конверсии между этими группами.
  • Тест будет двусторонний, т.к. в условии задачи не указано, в какую сторону должно быть различие.
  • Порог статистической значимости alpha возьмем равным 0,05.
  • Проверять статистическое различие между выборками мы будем путем сравнения их долей в общей совокупности. Для этого лучше всего подходит z-тест. Перед применением z-теста проведем несколько подготовительных вычислений, найдем количества пользователей по группам для каждого события и найдем их доли.

Найдем количество уников для кликеров и некликеров:

In [45]:
# дафтафрейм с пользователем, событием и количеством этого события для пользователя
user_event_n = data.groupby(['user_id', 'event_name'],as_index=0).count()
display(user_event_n.head())
user_id event_name event_time date
0 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 map 6 6
1 0001b1d5-b74a-4cbf-aeb0-7df5947bf349 tips_show 29 29
2 00157779-810c-4498-9e05-a1e9e3cedf93 advert_open 2 2
3 00157779-810c-4498-9e05-a1e9e3cedf93 contacts_call 5 5
4 00157779-810c-4498-9e05-a1e9e3cedf93 contacts_show 11 11
In [46]:
# количество кликеров
both_events = (user_event_n                   # датафрейм всех пользователей, кто совершал хотя бы одно из событий tips_show или tips_click
               .query('event_name == "tips_show" or event_name == "tips_click"')
               .groupby('user_id',as_index=0).count()
              )
clickers_n = both_events.query('event_name == 2')['user_id'].count()      # выбор и подсчет пользователей с обеими событиями
print('Количество кликеров:', clickers_n)
Количество кликеров: 297
In [47]:
# количество некликеров
nonclickers_and_clickers = user_event_n.query('event_name == "tips_show"')   # датафрейм всех, кто совершал tips_show (содержит также и кликеров)
nonclickers_n = len(nonclickers_and_clickers) - clickers_n         # вычитаем кликеров из общего числа
print('Количество некликеров:', nonclickers_n)
Количество некликеров: 2504

Найдем количество пользователей, из кликеров и некликеров, которые совершали contacts_show

In [48]:
# количество целевых кликеров
three_events = (user_event_n                   
                .query('event_name == "tips_show" or event_name == "tips_click" or event_name == "contacts_show"')
                .groupby('user_id',as_index=0).count()
               )
target_clickers_n = three_events.query('event_name == 3')['user_id'].count()
print('Количество целевых кликеров:', target_clickers_n)
Количество целевых кликеров: 91
In [49]:
# количество целевых некликеров
target_nonclickers_and_clickers = (user_event_n
                            .query('event_name == "tips_show" or event_name == "contacts_show"')
                            .groupby('user_id',as_index=0).count()
                           )

target_nonclickers_n = target_nonclickers_and_clickers.query('event_name == 2')['user_id'].count() - target_clickers_n
print('Количество целевых некликеров:', target_nonclickers_n)
Количество целевых некликеров: 425

Проверим гипотезу:

In [50]:
# функция для проверки z-теста
def z_test(target_users_n, users_n, target_nonusers_n, nonusers_n):

    alpha = 0.05                                # порог статистической значимости
    p1 = target_users_n / users_n               # конверсия первой группы
    p2 = target_nonusers_n / nonusers_n            # # конверсия второй группы
    p_combined = (target_users_n + target_nonusers_n) / (users_n + nonusers_n)      # конверсия общая

# расчет z-критерия
    z_value = (p1 - p2) / mth.sqrt(p_combined * (1 - p_combined) * (1/users_n + 1/nonusers_n))
# задаем стандартное нормальное распределение (среднее 0, ст.отклонение 1)
    distr = st.norm(loc=0, scale=1)
# считаем p-value
    p_value = (1 - distr.cdf(abs(z_value))) * 2
    print('p-значение: ', p_value)
    if p_value < alpha:
        print('Отвергаем нулевую гипотезу')
    else:
        print('Не получилось отвергнуть нулевую гипотезу')

# проверка гипотезы
z_test(target_clickers_n, clickers_n, target_nonclickers_n, nonclickers_n)
p-значение:  9.218316554537864e-09
Отвергаем нулевую гипотезу
In [51]:
# конверсия групп
print('Конверсия целевой группы: ', 100*target_clickers_n / clickers_n, '%'
      , '\nКонверсия нецелевой группы: ', 100*target_nonclickers_n / nonclickers_n, '%')
# различие конверсий
print('\nОтношение конверсий: ',(target_clickers_n/clickers_n)/(target_nonclickers_n/nonclickers_n))
Конверсия целевой группы:  30.63973063973064 % 
Конверсия нецелевой группы:  16.972843450479232 %

Отношение конверсий:  1.8052208358090711

Проверка показывает, что между конверсиями двух групп существует статистически значимая разница - пользователи, совершающие tips_click, чаще в 1,8 раза просматривают контакты, чем те, кто просто совершает событие tips_show.
Это можно обосновать тем, что пользователь, раз он кликнул на рекламное объявление, уже заинтересовался данным товаром, и, соответственно, с большей вероятностью купит его.

Проверка гипотезы о различии конверсии в просмотр контактов у двух групп пользователей: те, которые совершают действия photos_show, и те, которые не совершают photos_show.¶

  • Нулевая гипотеза, Но, - гипотеза о равенстве конверсии между теми, кто совершают photos_show и теми, кто не просматривает фотографии, в просмотр контактов (contacts_show).
  • Альтернативная гипотеза, На, - гипотеза о различии в конверсии между этими группами.
  • Также, как и в предыдущем пункте, тест - двусторонний, порог статистической значимости alpha равен 0,05.
  • Проверка проводится с помощью z-критерия.
In [52]:
# количество пользователей, совершавших `photos_show`, и количество пользователей совершавших так же `contacts_show`
photouser_n = len(user_event_n.query('event_name == "photos_show"'))
               
target_photouser = (user_event_n
               .query('event_name == "photos_show" or event_name == "contacts_show"')
               .groupby('user_id', as_index=0).count()
              )
target_photouser_n = target_photouser.query('event_name == 2')['user_id'].count()
print('Количество фотоюзеров:         ', photouser_n, '\nКоличество целевых фотоюзеров:  ', target_photouser_n)
Количество фотоюзеров:          1095 
Количество целевых фотоюзеров:   339
In [53]:
# количество пользователей, не совершавших `photos_show`, и количество пользователей совершавших так же `contacts_show`
nonphotouser_n = len(source) - photouser_n

target_nonphotouser_n = user_event_n.query('event_name == "contacts_show"')['user_id'].count() - target_photouser_n
print('Количество нефотоюзеров:         ', nonphotouser_n, '\nКоличество целевых нефотоюзеров:  ', target_nonphotouser_n)
Количество нефотоюзеров:          3198 
Количество целевых нефотоюзеров:   642

Проверяем гипотезу:

In [54]:
z_test(target_photouser_n, photouser_n, target_nonphotouser_n, nonphotouser_n)
p-значение:  1.3278267374516872e-13
Отвергаем нулевую гипотезу
In [55]:
# конверсия групп
print('Конверсия целевой группы: ', 100*target_photouser_n / photouser_n, '%'
      , '\nКонверсия нецелевой группы: ', 100*target_nonphotouser_n / nonphotouser_n, '%')
# различие конверсий
print('\nОтношение конверсий: ',(target_photouser_n/photouser_n)/(target_nonphotouser_n/nonphotouser_n))
Конверсия целевой группы:  30.958904109589042 % 
Конверсия нецелевой группы:  20.075046904315197 %

Отношение конверсий:  1.5421584944309308

Различие в конверсии - 1,5 раза, и это статистически значимое различие. Пользователи, просматривающие фотографии в объявлении, в полтора раза чаще интересуются контактами продавца, чем те пользователи, кто не смотрит фотографии.
Пользователь обычно не смотрит фотографии просто так, его интересует предлагаемый или разыскиваемый товар - он сравнивает и выбирает,он присматривает (в том числе и при помощи фотографий), т.е. он уже готов его купить.


Общие выводы¶

к заголовку

Задача проекта - выяснить, как можно улучшить мобильное приложение "Ненужные вещи" на основе анализа данных о поведении пользователей в приложении.
Входные данные представляют собой два датасета с 70000+ событий, совершенных 4000+ пользователями за четыре недели, начиная с 07.10.2019 года.

На этапе загрузки и предобработки данные были проверены на пропуски и дубликаты, было приведено к одному названию событие просмотра контактов, contacts_show, указанное в некоторых ячейках как show_contacts.

На этапе исследовательского анализа мы объединили отдельные события в датафрейме в сессии, чтобы понять, какие последовательности действий выполняет пользователь, перед тем как совершить событие, указанное в задании для проекта, как целевое - contacts_show, просмотр контактов.

На этапе основных вопросов исследования было отобрано четыре наиболее распространенных сценария (последовательности действий), таких, чтобы последним действием являлось целевое. Для них была вычислена конверсия для каждого шага и визуализация в виде воронок конверсии.
Лучшую конверсию из этих четырех показал сценарий photos_show -> contacts_show - 30%. Пользователи с большей вероятностью доберутся до контактов продавца, если будут просматривать больше фотографий.
Худшая конверсия при переходе от показа рекомендаций или от поиска - 20%. Возможно, приложение показывает пользователю не совсем тот товар, который он хотел бы видеть.
Также была построена диаграмма относительной частоты событий для тех пользователей, кто совершил contacts_show, и тех, кто не совершал. Согласно визуализации, пользователи, совершившие contacts_show, чаще просматривают фотографии и кликают на рекомендации. Т.е. выбор продукта происходит, в основном, благодаря его фотографиям.

На этапе проверки гипотез мы проверили методами статистики гипотезы о том, что у пользователей, совершающих действия tips_click и photos_show (две разные гипотезы и проверки), конверсия в целевое действие "просмотр контактов" различается с остальными пользователями. Статистически значимые различия в конверсии для этих двух событий следующие:
tips_click - 1,8 раза;
photos_show - 1,5 раза.
Это является, скорее всего, следствием активного поиска продукта пользователями. Они больше сравнивают по фотографиям, кликают по рекомендациям и, в результате, с большей вероятностью узнают контакты продавца и покупают товар.

Рекомендации
Результаты анализа поведения пользователей в приложении показывают, что для того, чтобы улучшить конверсию в целевое действие и удобство пользования приложением, следует:

  • доработать рекомендательные алгоритмы приложения, чтобы более точно предугадать запросы пользователя;
  • доработать поисковые алгоритмы приложения, чтобы выводить рекомендации более соответствующие поисковому запросу;
  • поэкспериментировать с просмотром фотографий - возможно там еще есть потенциал для увеличения конверсии.